package com.corpgovernment.common.utils;

import static com.ctrip.corp.obt.generic.constants.GenericConstants.UNKNOWN;
import static com.ctrip.corp.obt.generic.utils.Conditional.ofNullable;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.cert.CertificateException;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import com.corpgovernment.common.logging.service.LoggerService;
import com.corpgovernment.common.logging.service.LoggerServiceFactory;
import com.corpgovernment.common.supplier.ReplaceSupplierUidUtil;
import com.corpgovernment.common.supplier.SupplierSign;
import com.ctrip.corp.obt.generic.utils.JsonUtils;
import com.ctrip.corp.obt.generic.utils.StringUtils;
import com.ctrip.corp.obt.metric.Metrics;
import com.ctrip.corp.obt.metric.log.model.MetricMeta;
import com.ctrip.corp.obt.metric.spectator.api.Id;
import com.ctrip.framework.apollo.Config;
import com.ctrip.framework.apollo.ConfigService;
import com.fasterxml.jackson.annotation.JsonAlias;

import lombok.SneakyThrows;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * connectTimeout 连接超时 默认10秒 readTimeout 读超时 默认10秒 writeTimeout 写超时 默认10秒
 * 注意：此工具类为调用供应商专用HttpUtils，由于历史原因，工具类命名存在问题
 * 若需要使用纯净版Http工具类，请使用 com.ctrip.corp.obt.async.http.AsyncHttpClient
 */
public class HttpUtils {
    private static final LoggerService LOGGER = LoggerServiceFactory.getLoggerService(HttpUtils.class);
    private final static String TITLE = "getClient";
    public static final String ARCH_SECURITY_HTTP_MASK = "arch.security.http.mask";

    // private static OkHttpClient client = null;

    public static MediaType JSON = MediaType.get("application/json; charset=utf-8");
    public static MediaType URLENCODED = MediaType.get("application/x-www-form-urlencoded; charset=utf-8");
    private static Id supplierApiInvokeErrorId;
    private static final Id httpPerformanceId = Metrics.REGISTRY.createId("supplier.api.invoke.costTime");

    /**
     * 代理的map集合
     */
    private final static ConcurrentHashMap<String, OkHttpClient> proxyCollection = new ConcurrentHashMap<>();

    /**
     * 代理url
     */
    private static final String proxyUrl = "proxyUrl";

    /**
     * 代理端口
     */
    private static final String proxyPort = "proxyPort";

    private static final String LINE_SEPARATOR = "\r\n";

    static {
        proxyCollection.put("disableProxy", new OkHttpClient.Builder().connectTimeout(60000L, TimeUnit.MILLISECONDS)
                .readTimeout(60000L, TimeUnit.MILLISECONDS).build());
        supplierApiInvokeErrorId = Metrics.REGISTRY.createId("supplier.api.invoke.failed");
    }

    /**
     * 新建http根据apollo配置 是否需要代理。
     *
     * @return
     */
    public static OkHttpClient getClient() {
        return getClient(false);
    }

    /**
     * 新建http根据apollo配置 是否需要代理。
     *
     * @return
     */
    @SneakyThrows
    public static OkHttpClient getClient(boolean trustAllCerts) {
        Config config = ConfigService.getConfig("corpgovernment.common");
        String serverUrl = config.getProperty(proxyUrl, "");
        String serverPort = config.getProperty(proxyPort, "");
        LOGGER.info(TITLE, "获取apollo是否配置代理，代理url:{},代理端口：{}", serverUrl, serverPort);

        // 创建一个信任所有证书的TrustManager
        TrustManager[] trustAllCertsManager = new TrustManager[] {new X509TrustManager() {
            @Override
            public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType)
                    throws CertificateException {}

            @Override
            public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType)
                    throws CertificateException {}

            @Override
            public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                return new java.security.cert.X509Certificate[] {};
            }
        }};

        // 创建一个SSLContext，使用上面定义的TrustManager
        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustAllCertsManager, new java.security.SecureRandom());

        if (StringUtils.isBlank(serverUrl) || StringUtils.isBlank(serverPort)) {
            LOGGER.info(TITLE, "获取apollo是否配置代理，代理url:{},代理端口：{},无需代理", serverUrl, serverPort);
            OkHttpClient client = proxyCollection.get("disableProxy");
            if (client == null) {
                client = new OkHttpClient.Builder().connectTimeout(60000L, TimeUnit.MILLISECONDS)
                        .readTimeout(60000L, TimeUnit.MILLISECONDS).build();
                proxyCollection.put("disableProxy", client);
            }
            return client;
        } else {
            String trustAllCertsPrefix = trustAllCerts ? "trustAllCerts_" : "";
            OkHttpClient client = proxyCollection.get(trustAllCertsPrefix + serverUrl);
            LOGGER.info(TITLE, "获取apollo是否配置代理，代理url:{},代理端口：{},加载代理", serverUrl, serverPort);
            if (client == null) {
                Proxy proxy =
                        new Proxy(Proxy.Type.HTTP, new InetSocketAddress(serverUrl, Integer.parseInt(serverPort)));
                if (trustAllCerts) {
                    LOGGER.info(TITLE, "获取apollo是否配置代理，代理url:{},代理端口：{},无需代理", serverUrl, serverPort);
                    client = new OkHttpClient.Builder().connectTimeout(60000L, TimeUnit.MILLISECONDS)
                            .proxy(proxy).readTimeout(60000L, TimeUnit.MILLISECONDS)
                            .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager)trustAllCertsManager[0])
                            .hostnameVerifier((hostname, session) -> true)
                            .build();
                    proxyCollection.put("trustAllCerts_" + serverUrl, client);
                    return client;
                }
                client = new OkHttpClient.Builder().connectTimeout(60000L, TimeUnit.MILLISECONDS)
                        .proxy(proxy).readTimeout(60000L, TimeUnit.MILLISECONDS).build();
                LOGGER.info(TITLE, "获取apollo是否配置代理，代理url:{},代理端口：{},初始化代理", serverUrl, serverPort);
                proxyCollection.put(serverUrl, client);
            }
            return client;
        }
    }

    /**
     * 日志记录
     */
    public static void logHttp(String supplier, String name, long elapsed, String requestUrl, String requestBody,
            String responseBody) {
        StringBuilder sb = new StringBuilder();
        sb.append("服务方：").append(supplier);
        sb.append(LINE_SEPARATOR).append("名称：").append(name);
        sb.append(LINE_SEPARATOR).append("耗时：").append(elapsed).append("毫秒");
        sb.append(LINE_SEPARATOR).append("地址：").append(requestUrl);
        if (StringUtils.isNotBlank(requestBody)) {
            sb.append(LINE_SEPARATOR).append("请求：").append(requestBody);
        }
        sb.append(LINE_SEPARATOR).append("响应：").append(responseBody);
        SupplierResponse response;
        try {
            response = JsonUtils.parse(responseBody, SupplierResponse.class);
        } catch (Exception ex) {
            response = null;
        }
        Metrics.REGISTRY.distributionSummary(httpPerformanceId
                        .withTag("supplier", ofNullable(supplier).orElse(UNKNOWN))
                        .withTag("name", ofNullable(name).orElse(UNKNOWN)))
                .record(elapsed);
        Metrics.REGISTRY.counter(supplierApiInvokeErrorId
                        .withTag("supplier", ofNullable(supplier).orElse(UNKNOWN))
                        .withTag("name", ofNullable(name).orElse(UNKNOWN)))
                .increment();
        Integer errorCode = null;
        String errorMessage = null;
        if (null != response && response.isError()) {
            errorCode = response.getErrorCode();
            errorMessage = response.getErrorMessage();
            Metrics.LOGGER.collect(new MetricMeta("supplier.api.invoke.failed")
                    .withTag("errorCode", ofNullable(errorCode).map(Objects::toString).orElse(UNKNOWN))
                    .withTag("errorMessage", ofNullable(errorMessage).orElse(UNKNOWN))
                    .withTag("supplier", ofNullable(supplier).orElse(UNKNOWN))
                    .withTag("name", ofNullable(name).orElse(UNKNOWN))
                    .withTag("requestUrl", ofNullable(requestUrl).orElse(UNKNOWN))
                    .setMessage(sb.toString()));
        } else {
            LOGGER.info("httpResponse", sb.toString());
        }
        // log if requestBody has mask
        if (maskMatches(requestBody)) {
            Metrics.LOGGER.collect(new MetricMeta(ARCH_SECURITY_HTTP_MASK)
                    .withTag("errorCode", ofNullable(errorCode).map(Objects::toString).orElse(UNKNOWN))
                    .withTag("errorMessage", ofNullable(errorMessage).orElse(UNKNOWN))
                    .withTag("supplier", ofNullable(supplier).orElse(UNKNOWN))
                    .withTag("name", ofNullable(name).orElse(UNKNOWN))
                    .withTag("requestUrl", ofNullable(requestUrl).orElse(UNKNOWN))
                    .setMessage(sb.toString()));
        }
    }

    /**
     * predicate whether if data contains mask
     * @param data
     * @return
     */
    private static boolean maskMatches(String data) {
        return data.contains("*");
    }

    static class SupplierResponse {

        @JsonAlias({"errno", "errorCode"})
        private Integer resultCode;
        @JsonAlias({"errmsg", "errorMessage"})
        private String message;

        private Boolean success;

        private Status status;

        String getErrorMessage() {
            return Optional.ofNullable(this.message)
                    .orElse(Optional.ofNullable(this.status).map(Status::getMessage).orElse(null));
        }

        Integer getErrorCode() {
            return Optional.ofNullable(this.resultCode)
                    .orElse(Optional.ofNullable(this.status).map(Status::getResultCode).orElse(null));
        }

        public boolean isError() {
            if (null != this.success && this.success) {
                return false;
            }
            if (null != this.message && null != this.resultCode) {
                return !this.resultCode.equals(0);
            }
            if (this.status == null) {
                return false;
            }
            if (null != this.status.getSuccess() && this.status.getSuccess()) {
                return false;
            }
            if (null != this.status.getMessage() && null != this.status.getResultCode()) {
                return !status.getResultCode().equals(0);
            }
            return false;
        }

        public Integer getResultCode() {
            return resultCode;
        }

        public void setResultCode(Integer resultCode) {
            this.resultCode = resultCode;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public Status getStatus() {
            return status;
        }

        public void setStatus(Status status) {
            this.status = status;
        }

        public Boolean getSuccess() {
            return success;
        }

        public void setSuccess(Boolean success) {
            this.success = success;
        }
    }

    static class Status {
        @JsonAlias({"errno", "errorCode"})
        private Integer resultCode;
        @JsonAlias({"errmsg", "errorMessage", "message"})
        private String message;

        private Boolean success;

        public Integer getResultCode() {
            return resultCode;
        }

        public void setResultCode(Integer resultCode) {
            this.resultCode = resultCode;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public Boolean getSuccess() {
            return success;
        }

        public void setSuccess(Boolean success) {
            this.success = success;
        }
    }

    /**
     * GET 请求
     */
    public static String doGet(String supplier, String name, String requestUrl, String requestBody) throws IOException {
        long start = System.currentTimeMillis();
        if (StringUtils.isNotBlank(requestBody)) {
            requestUrl = new StringBuilder(requestUrl).append("?").append(requestBody).toString();
        }
        String responseBody = "";
        Request request = new Request.Builder().url(requestUrl).build();
        try (Response response = getClient().newCall(request).execute()) {
            responseBody = response.body().string();
            return responseBody;
        } finally {
            HttpUtils.logHttp(supplier, name, System.currentTimeMillis() - start, requestUrl, "", responseBody);
        }
    }

    /**
     * POST请求
     */
    public static String doPost(String supplier, String name, String requestUrl, String requestBody)
            throws IOException {
        long start = System.currentTimeMillis();
        RequestBody body = RequestBody.create(URLENCODED, requestBody);
        Request request = new Request.Builder().url(requestUrl).post(body).build();
        String responseBody = "";
        try (Response response = getClient().newCall(request).execute()) {
            responseBody = response.body().string();
            return responseBody;
        } finally {
            HttpUtils.logHttp(supplier, name, System.currentTimeMillis() - start, requestUrl, requestBody,
                    responseBody);
        }
    }

    /**
     * POST请求
     */
    public static String doPostTrustAllCerts(String supplier, String name, String requestUrl, String requestBody)
            throws IOException {
        long start = System.currentTimeMillis();
        RequestBody body = RequestBody.create(URLENCODED, requestBody);
        Request request = new Request.Builder().url(requestUrl).post(body).build();
        String responseBody = "";
        try (Response response = getClient(true).newCall(request).execute()) {
            responseBody = response.body().string();
            return responseBody;
        } finally {
            HttpUtils.logHttp(supplier, name, System.currentTimeMillis() - start, requestUrl, requestBody,
                    responseBody);
        }
    }

    /**
     * 使用签名POST请求
     */
    public static String doPostJSONUseSign(String supplier, String name, String requestUrl, String requestBody,
            String userKey, Map<String, Object>... maps) throws IOException {
        long start = System.currentTimeMillis();
        Map jsonObject = JsonUtils.parse(requestBody, Map.class);
        if (maps != null && maps.length > 0) {
            for (int i = 0; i < maps.length; i++) {
                if (maps[i] != null && maps[i].get("userKey") != null) {
                    userKey = String.valueOf(maps[i].get("userKey"));
                    maps[i].remove("userKey");
                    jsonObject.putAll(maps[i]);
                } else if (maps[i] != null) {
                    jsonObject.putAll(maps[i]);
                }
            }
        }
        // 创建签名
        Map<String, String> signInfo = SupplierSign.createSignInfo(userKey);
        if (signInfo != null) {
            jsonObject.putAll(signInfo);
        }
        requestBody = JsonUtils.toJsonString(jsonObject);
        //
        RequestBody body = RequestBody.create(JSON, requestBody);

        String responseBody = "";
        Response response = null;
        try {
            Request request = new Request.Builder().url(requestUrl).post(body).build();
            response = getClient().newCall(request).execute();
            responseBody = response.body().string();
            return responseBody;
        } finally {
            if (response != null) {
                response.close();
            }
            HttpUtils.logHttp(supplier, name, System.currentTimeMillis() - start, requestUrl, requestBody,
                    responseBody);
        }
    }

    /**
     * POST请求
     */
    public static String doPostJSON(String supplier, String name, String requestUrl, String requestBody)
            throws IOException {
        long start = System.currentTimeMillis();
        LOGGER.info("doPostJSON", "POST请求Url: {}", requestUrl);
        RequestBody body = RequestBody.create(JSON, requestBody);
        Request request = new Request.Builder().url(requestUrl).post(body).build();
        String responseBody = "";
        try (Response response = getClient().newCall(request).execute()) {
            responseBody = response.body().string();
            return responseBody;
        } finally {
            HttpUtils.logHttp(supplier, name, System.currentTimeMillis() - start, requestUrl, requestBody,
                    responseBody);
        }
    }

    /***
     * post调用接口返回对象
     * @param supplier
     * @param name
     * @param requestUrl
     * @param requestBodyObj
     * @param resultClass
     * @param <T>
     * @return
     * @throws IOException
     */
    public static <T> T doPostJSONGetT(String supplier, String name, String requestUrl, Object requestBodyObj,
            Class<T> resultClass) throws IOException {
        String resultJson = doPostJSON(supplier, name, requestUrl, JsonUtils.toJsonString(requestBodyObj));
        if (StringUtils.isBlank(resultJson)) {
            return null;
        }
        return JsonUtils.parse(resultJson, resultClass);
    }

    /**
     * POST请求
     */
    public static String doPostJSONHeader(String supplier, String name, String requestUrl, String requestBody,
            Map<String, String> headers) throws IOException {
        long start = System.currentTimeMillis();
        RequestBody body = RequestBody.create(JSON, requestBody);
        Request.Builder builder = new Request.Builder().url(requestUrl).post(body);
        for (Map.Entry<String, String> entry : headers.entrySet()) {
            builder.addHeader(entry.getKey(), entry.getValue());
        }
        Request request = builder.build();
        String responseBody = "";
        try (Response response = getClient().newCall(request).execute()) {
            responseBody = response.body().string();
            return responseBody;
        } finally {
            HttpUtils.logHttp(supplier, name, System.currentTimeMillis() - start, requestUrl, requestBody,
                    responseBody);
        }
    }

    /**
     * 普通(非供应商请求:获取token)
     *
     * @param requestUrl
     * @param requestBody
     * @return
     * @throws IOException
     */
    public static String doPostJSON(String requestUrl, String requestBody) throws IOException {
        RequestBody body = RequestBody.create(JSON, requestBody);
        Request request = new Request.Builder().url(requestUrl).post(body).build();
        Response response = getClient().newCall(request).execute();
        return response.body().string();
    }

    /**
     * Create by: xks
     * Description: 带请headers   post请求
     * Create time: 2022/3/28 20:14
     * Params: [requestUrl, requestBody, headers]
     * Return java.lang.String
     */
    public static String doPostJSON(String requestUrl, String requestBody, Map<String, String> headers)
            throws IOException {
        RequestBody body = RequestBody.create(JSON, requestBody);
        Request.Builder builder = new Request.Builder().url(requestUrl).post(body);
        if (!Objects.isNull(headers)) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                builder.addHeader(entry.getKey(), entry.getValue());
            }
        }
        Request request = builder.build();
        Response response = getClient().newCall(request).execute();
        return response.body().string();
    }

    public static String doPostJSONUseSignWithoutEncoding(String supplier, String name, String requestUrl,
            String requestBody, String userKey, Map<String, Object>... maps) throws IOException {
        long start = System.currentTimeMillis();
        Map jsonObject = JsonUtils.parse(requestBody, Map.class);
        if (maps != null && maps.length > 0) {
            for (int i = 0; i < maps.length; i++) {
                if (maps[i] != null && maps[i].get("userKey") != null) {
                    userKey = String.valueOf(maps[i].get("userKey"));
                    maps[i].remove("userKey");
                    jsonObject.putAll(maps[i]);
                } else if (maps[i] != null) {
                    jsonObject.putAll(maps[i]);
                }
            }
        }
        // 创建签名
        Map<String, String> signInfo = SupplierSign.createSignInfo(userKey);
        if (signInfo != null) {
            jsonObject.putAll(signInfo);
        }
        requestBody = JsonUtils.toJsonString(jsonObject);
        //
        RequestBody body = RequestBody.create(JSON, requestBody);

        String responseBody = "";
        Response response = null;
        try {
            Request request =
                    new Request.Builder().url(requestUrl).post(body).addHeader("Accept-Encoding", "identity").build();
            response = getClient().newCall(request).execute();
            responseBody = response.body().string();
            return responseBody;
        } finally {
            if (response != null) {
                response.close();
            }
            HttpUtils.logHttp(supplier, name, System.currentTimeMillis() - start, requestUrl, requestBody,
                    responseBody);
        }
    }

    /**
     * post rpc
     * 当调用供应商为携程时候，使用员工卡号进行操作, 其他供应商不会处理
     * 使用范围：例如酒店列表查询时候，入参参数中有corpId和uid这两个参数是携程提供的，此时我们员工已经在corpId下进行了开卡处理，这时候我们需要将uid替换成开卡得卡号
     * @param supplier
     * @param name
     * @param requestUrl
     * @param requestBody
     * @return
     */
    public static String doPostJSONReplaceSupplierCardNo(String supplier, String name, String requestUrl,
            String requestBody) throws IOException {
        long start = System.currentTimeMillis();
        requestBody = ReplaceSupplierUidUtil.replaceEmployeeId(supplier, requestBody);
        RequestBody body = RequestBody.create(JSON, requestBody);
        Request request = new Request.Builder().url(requestUrl).post(body).build();
        String responseBody = "";
        try (Response response = getClient().newCall(request).execute()) {
            responseBody = response.body().string();
            return responseBody;
        } finally {
            HttpUtils.logHttp(supplier, name, System.currentTimeMillis() - start, requestUrl, requestBody,
                    responseBody);
        }
    }

    public static String doPostJSONUseSignReplaceSupplierCardNo(String supplier, String name, String requestUrl,
            String requestBody, String userKey, Map<String, Object>... maps) throws IOException {
        long start = System.currentTimeMillis();
        requestBody = ReplaceSupplierUidUtil.replaceEmployeeId(supplier, requestBody);
        Map jsonObject = JsonUtils.parse(requestBody, Map.class);
        if (maps != null && maps.length > 0) {
            for (int i = 0; i < maps.length; i++) {
                if (maps[i] != null && maps[i].get("userKey") != null) {
                    userKey = String.valueOf(maps[i].get("userKey"));
                    maps[i].remove("userKey");
                    jsonObject.putAll(maps[i]);
                } else if (maps[i] != null) {
                    jsonObject.putAll(maps[i]);
                }
            }
        }
        // 创建签名
        Map<String, String> signInfo = SupplierSign.createSignInfo(userKey);
        if (signInfo != null) {
            jsonObject.putAll(signInfo);
        }
        requestBody = JsonUtils.toJsonString(jsonObject);
        //
        RequestBody body = RequestBody.create(JSON, requestBody);

        String responseBody = "";
        Response response = null;
        try {
            Request request = new Request.Builder().url(requestUrl).post(body).build();
            response = getClient().newCall(request).execute();
            responseBody = response.body().string();
            return responseBody;
        } finally {
            if (response != null) {
                response.close();
            }
            HttpUtils.logHttp(supplier, name, System.currentTimeMillis() - start, requestUrl, requestBody,
                    responseBody);
        }
    }

}